/*
 *  voice.c
 *
 *  Copyright 2007	Robert Adkins
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License, version 2.1
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to
 *	Free Software Foundation, Inc.
 *	51 Franklin St., Fifth Floor
 *	Boston, MA 02110, USA.
 *
 *  This program is indebted to earlier work by Roger Butenuth and
 *  Michael Gorse who created Eflite, the speach synthesizer interface
 *  for Emacspeak.  The current program is a redesigned and rewritten
 *  implementation of Eflite's interface to CMU's Festival Lite speech
 *  synthesizer.  Note that the rewritten program is licensed under
 *  LGPL instead of the GPL to allow it to be incorporated seamlessly
 *  into LGPL libraries like vialog.
 * 
 *  This program is also indebted to T. V. Raman, the author of
 *  Emacspeak, for his profound contributions toward making software
 *  useable by the visually impaired.
 *
 */

#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>

#include "vialog.h"
#include "voice.h"

/* globals */
#if defined(USE_FLITE)
#define VOLUME_INCREMENT 100
#define SPEED_INCREMENT 100
#define PITCH_INCREMENT 100
#elif defined(USE_ESPEAK)
#define VOLUME_INCREMENT 5
#define SPEED_INCREMENT 10
#define PITCH_INCREMENT 5
#endif

/* This table basically stolen from Vocal-Eyes, and I am not even sure if it
 * is correct under Linux. */
/* tbd - use Speak-up's table instead */
char *ascii[256] = { "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "bang", "quote", "number", "dollar", "percent", "and", "apostrophe", "left paren", "right paren", "star", "plus", "comma", "dash", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less than", "equals", "greater than", "question", "at", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "left bracket", "backslash", "right bracket", "caret", "underline", "accent", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "left brace", "bar", "right brace", "tilda", "cap delta", "cap cedila", "u de arisis", "e accent a u", "a circumflex", "a de arisis", "accented a", "a with small circle accent", "cedila", "e circumflex", "e de arisis", "accented e", "i de arisis", "circumflex", "accented i", "cap a de arisis", "cap a with small circle accent", "cap e accent a u", "small a e", "cap a e", "o circumflex", "o de arisis", "accented o", "u circumflex", "accented u", "y de arisis", "cap o de arisis", "cap u de arisis", "cents sign", "pounds", "yen", "p sub t", "fancy f", "a accent a u", "i accent a u", "o accent a u", "u accent a u", "tilded n", "tilded cap n", "bar under a", "bar under o", "up side down question mark", "short horizontal with short left down", "short horizontal with short right down", "e half", "one quarter", "up side down exclamation point", "much less than", "much greater than", "dark shading", "medium shading", "light shading", "vertical bar", "vertical with centered left joint", "vertical with centered double left joint", "double vertical with centered left joint", "upper right corner with double vertical", "upper right corner with double horizontal", "double vertical with centered double left joint", "double vertical", "double upper right corner", "double lower right corner", "lower right corner with double vertical", "lower right corner with double horizontal", "upper right corner", "lower left corner", "horizontal with centered up joint", "horizontal with centered down joint", "vertical with centered right joint", "horizontal", "cross bars", "vertical bar with centered double right joint", "double vertical with centered right joint", "double lower left corner", "double upper left corner", "double horizontal with centered double up joint", "double horizontal with centered double down joint", "double vertical with centered double right joint", "double horizontal", "double cross form", "double horizontal with centered up joint", "horizontal with centered up joint", "double horizontal with centered down joint", "horizontal with centered double down joint", "lower left corner with double vertical", "lower left corner with double horizontal", "per left corner with double horizontal", "upper left corner with double vertical", "cross with double vertical", "cross with double horizontal", "lower right corner", "upper left corner", "filled square", "filled lower half", "filled left half", "filled right half", "filled upper half", "alpha", "beta", "cap gamma", "pi", "cap sigma", "sigma", "mu", "tau", "cap phi", "theta", "cap omega", "delta", "infinity", "phi", "epsilon", "intersection", "is identical to", "plus minus", "greater than or equal", "less than or equal", "upper part of integral", "lower part of integral", "divide", "approximately equal", "degrees", "small dot", "tiny dot", "square root", "exponent n", "exponent 2", "filled square", "ascii 255" };

/* adaptation for use with file paths */
char *ascii2[256] = { "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "bang", "quote", "number", "dollar", "percent", "and", "apostrophe", "left paren", "right paren", "star", "plus", "comma", "dash", "dot", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less than", "equals", "greater than", "question", "at", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "left bracket", "backslash", "right bracket", "caret", "underline", "accent", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "left brace", "bar", "right brace", "tilda", "cap delta", "ascii 128", "ascii 129", "ascii 130", "ascii 131", "ascii 132", "ascii 133", "ascii 134", "ascii 135", "ascii 136", "ascii 137", "ascii 138", "ascii 139", "ascii 140", "ascii 141", "ascii 142", "ascii 143", "ascii 144", "ascii 145", "ascii 146", "ascii 147", "ascii 148", "ascii 149", "ascii 150", "ascii 151", "ascii 152", "ascii 153", "ascii 154", "ascii 155", "ascii 156", "ascii 157", "ascii 158", "ascii 159", "ascii 160", "ascii 161", "ascii 162", "ascii 163", "ascii 164", "ascii 165", "ascii 166", "ascii 167", "ascii 168", "ascii 169", "ascii 170", "ascii 171", "ascii 172", "ascii 173", "ascii 174", "ascii 175", "ascii 176", "ascii 177", "ascii 178", "ascii 179", "ascii 180", "ascii 181", "ascii 182", "ascii 183", "ascii 184", "ascii 185", "ascii 186", "ascii 187", "ascii 188", "ascii 189", "ascii 190", "ascii 191", "ascii 192", "ascii 193", "ascii 194", "ascii 195", "ascii 196", "ascii 197", "ascii 198", "ascii 199", "ascii 200", "ascii 201", "ascii 202", "ascii 203", "ascii 204", "ascii 205", "ascii 206", "ascii 207", "ascii 208", "ascii 209", "ascii 210", "ascii 211", "ascii 212", "ascii 213", "ascii 214", "ascii 215", "ascii 216", "ascii 217", "ascii 218", "ascii 219", "ascii 220", "ascii 221", "ascii 222", "ascii 223", "ascii 224", "ascii 225", "ascii 226", "ascii 227", "ascii 228", "ascii 229", "ascii 230", "ascii 231", "ascii 232", "ascii 233", "ascii 234", "ascii 235", "ascii 236", "ascii 237", "ascii 238", "ascii 239", "ascii 240", "ascii 241", "ascii 242", "ascii 243", "ascii 244", "ascii 245", "ascii 246", "ascii 247", "ascii 248", "ascii 249", "ascii 250", "ascii 251", "ascii 252", "ascii 253", "ascii 254", "ascii 255" };

int numclients = 0;	/* Number of clients active */
static int tone_volume;
static int tone_flags = 3;	/* 0x01 == speaker, 0x02 = sound card */
lang_descr_t languages[LANGUAGES] = {
    { LANG_BRITISH_ENGLISH, "British English" },
    { LANG_DUMMY, "no language" }
};
static synth_state_t synthapi_state;
static synth_t synthapi = {
        &synthapi_state, /* synth_state_p state */
        &languages[LANG_BRITISH_ENGLISH],/* lang_descr_t *lang */
        "FLite/US English", /* char *name */
        NULL, 		/* void *lib_handle */
        s_close, 	/* int (*close)(struct synth_struct *s) */
        s_synth, 	/* int (*synth)(struct synth_struct *s, unsigned char *buffer) */
        NULL, 		/* int (*flush)(struct synth_struct *s) */
        s_clear, 	/* int (*clear)(struct synth_struct *s) */
        NULL,    	/* int (*index_set)(struct synth_struct *s) */
        NULL,    	/* int (*index_wait)(struct synth_struct *s, int id, int timeout) */
        s_get_param, 	/* int (*get_param)(struct synth_struct *s, synth_par_t par, int *value) */
        s_set_param 	/* int (*set_param)(struct synth_struct *s, synth_par_t par, int value) */
};
static FILE    *debug_fp = NULL;
int rc_dirty = 0;
FILE *my_output = 0;	/* prefer to stdout, to support --stdout */
synth_t *synthptr;
/* S_SPEED, S_PITCH, S_VOLUME */
#if defined(USE_FLITE)
int default_param[NPARAMS] = { 1000, 1000, 200 };
#elif defined(USE_ESPEAK)
int default_param[NPARAMS] = { 170, 50, 25 };
#endif
CLIENT speech_client;
setting *settings = NULL;
char *punct_some = NULL;	/* List of punctuation to speak when "some" selected */
char *punct_all = NULL;		/* List of punctuation to speak when "all" selected */
SAY_WHAT say_what = SAY_ALL;

static pthread_t wave_thread;
static pthread_t text_thread;
static int runSynthesize = 1;
static char *text;
static int text_size;
static pthread_attr_t ta;	/* for creating threads */
static int s_count = 0;
char p_text[BUFSIZE];
static void * synthesize(void *);
#if defined(USE_FLITE)
static cst_voice *v = NULL;
cst_audiodev *audiodev = NULL;	/* Sound device */
static float time_left = 0;
static void cst_wave_free(cst_wave *);
static void * play(synth_t *, cst_wave *);
#endif
/* BEGIN QUEUE MANAGER */
#define bTRUE  1
#define bFALSE 0
#define QUEUESIZE 100
//#define QUEUESIZE 2
typedef struct {
    char buf[QUEUESIZE][BUFSIZE];
    long head, tail;
    int full, empty;
    pthread_mutex_t *mut;
    pthread_cond_t *notFull, *notEmpty;
} tq;
tq *tqInit(void);
void tqDelete(tq *);
void tqAdd(tq *, const char *);
void tqGet(tq *, char *);

pthread_t pro, con;
tq *textfifo;

tq *tqInit(void)
{
    tq *q;

    q = (tq*) malloc(sizeof(tq));
    if (q == NULL)
        return (NULL);
    q->empty = bTRUE;
    q->full = bFALSE;
    q->head = q->tail = 0;
    q->mut = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t));
    pthread_mutex_init(q->mut, NULL);
    q->notFull = (pthread_cond_t*) malloc(sizeof(pthread_cond_t));
    pthread_cond_init(q->notFull, NULL);
    q->notEmpty = (pthread_cond_t*) malloc(sizeof(pthread_cond_t));
    pthread_cond_init(q->notEmpty, NULL);

    return (q);
}
void tqDelete(tq *q) /* order dependent, free allocated memory */
{
    pthread_mutex_destroy(q->mut);
    free(q->mut);
    pthread_cond_destroy(q->notFull);
    free(q->notFull);
    pthread_cond_destroy(q->notEmpty);
    free(q->notEmpty);
    free(q);
}
void tqAdd(tq *q, const char *in)
{
    dlg_log(LOG_LOGFILE, "tqAdd \"%s\" at %d", in, q->tail);
    strncpy(&q->buf[q->tail][0], in, BUFSIZE);
    q->tail++;
    if (q->tail == QUEUESIZE)
        q->tail = 0;
    if (q->tail == q->head) {
        dlg_log(LOG_LOGFILE, " *** Add q->tail == q->head == %d, q->full = TRUE", q->head);
        q->full = bTRUE;
    }
    q->empty = bFALSE;
    return;
}
void tqGet(tq *q, char *out)
{
    strncpy(out, &q->buf[q->head][0], BUFSIZE); 
    dlg_log(LOG_LOGFILE, "tqGet \"%s\" at %d", out, q->head);
    q->head++;

    if (q->head == QUEUESIZE)
        q->head = 0;
    if (q->head == q->tail) {
        dlg_log(LOG_LOGFILE, " *** Get q->tail == q->head == %d, q->empty = TRUE", q->head);
        q->empty = bTRUE;
    }
    q->full = bFALSE;

    return;
}
void tqEmpty(tq *q)
{
    q->empty = bTRUE;
    q->full = bFALSE;
    q->head = q->tail = 0;
}
/* END QUEUE MANAGER */
#if defined (USE_ESPEAK)
/* BEGIN ESPEAK VARIABLES */
int samplerate;
int synth_flags = espeakCHARS_AUTO | espeakPHONEMES | espeakENDPAUSE;
int volume = -1;
int speed = -1;
int pitch = -1;
int option_capitals = -1;
int option_punctuation = -1;
int option_linelength = 0;

#define N_PUNCTLIST  100
wchar_t option_punctlist[N_PUNCTLIST];
/* END ESPEAK VARIABLES */
#endif
/* BEGIN MODE STACK MANAGER */
#define MODE_STACK_SIZE 32
typedef struct istack {
    int where;
    int top[MODE_STACK_SIZE];
} istack_t;
static istack_t mode_stack = { .where = -1 };
int isfull_speak_mode(void);
int isempty_speak_mode(void);

void push_speak_mode(int mode) {
  if (!isfull_speak_mode())
      mode_stack.where++;
  mode_stack.top[mode_stack.where] = mode;
}
int pop_speak_mode() {
  int mode = mode_stack.top[mode_stack.where];
  if (isempty_speak_mode()) 
      mode = 0x7FFFFFFF;
  else
      mode_stack.where--;
  return mode;
}
int get_speak_mode() {
  if (!isempty_speak_mode()) 
      return mode_stack.top[mode_stack.where];
  return 0x7FFFFFFF;
}
void set_speak_mode(int mode) {
  if (!isempty_speak_mode()) 
      mode_stack.top[mode_stack.where] = mode;
}
int isfull_speak_mode() {
  return (mode_stack.where == MODE_STACK_SIZE);
}
int isempty_speak_mode() {
  return (mode_stack.where == -1);
}
int verbose_mode() {
  return (mode_stack.top[mode_stack.where] == SPEAK_VERBOSELY);
}
int succinct_mode() {
  return (mode_stack.top[mode_stack.where] == SPEAK_BRIEFLY);
}
/* END MODE STACK MANAGER */

synth_t *synth_open(void *context)
{
    synth_t *s;
    int     i, val;

    dlg_log(LOG_LOGFILE, "synth_open");
    debug_fp = stderr;
    textfifo = tqInit();
#if defined(USE_FLITE)
    time_left = 0;
    flite_init();
    v = (cst_voice *)REGISTER_VOX(NULL);
#elif defined(USE_ESPEAK)
    option_punctlist[0] = 0;
    samplerate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK,0,NULL);
    dlg_log(LOG_LOGFILE, "synth_open: espeak samplerate=%d", samplerate);
#endif
    pthread_attr_init(&ta);
    /*pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED);*/
    s = &synthapi;

    if (s != NULL && !s->state->initialized) {
        for (i = 0; i < NPARAMS; i++)
        {
          speech_client.param[i] = default_param[i];
          s_set_param(s, i, speech_client.param[i]);
        }
        s->state->initialized = 1;
    }
    pthread_create(&text_thread, &ta, synthesize, s);
    return s;
}

int is_rcfile_dirty()
{
	return
		speech_client.param[S_VOLUME] != default_param[S_VOLUME] ||
		speech_client.param[S_SPEED] != default_param[S_SPEED] ||
		speech_client.param[S_PITCH] != default_param[S_PITCH];
}

int set_volume_change_mode( int mode )
{
    dlg_log(LOG_LOGFILE, "set_volume_change_mode: new value=%d", mode);
    if (mode)
        say_it("Type plus or minus to change volume from current value of %d", speech_client.param[S_VOLUME]);
    else if (speech_client.change_mode[S_VOLUME] == 1 || verbose_mode())
        say_it("Volume control is turned off");
    speech_client.change_mode[S_VOLUME] = mode;
}

int get_volume_change_mode( void )
{
    dlg_log(LOG_LOGFILE, "get_volume_change_mode: value=%d", speech_client.change_mode[S_VOLUME]);
    return speech_client.change_mode[S_VOLUME];
}

int change_volume( int amount )
{
    int old_val = speech_client.param[S_VOLUME];
    speech_client.param[S_VOLUME] += amount;
    dlg_log(LOG_LOGFILE, "change_volume: old=%d, new=%d", old_val, speech_client.param[S_VOLUME]);
    say_it("Volume is now %d", speech_client.param[S_VOLUME]);
    return old_val;
}

int set_speed_change_mode( int mode )
{
    dlg_log(LOG_LOGFILE, "set_speed_change_mode: new value=%d", mode);
    if (mode)
        say_it("Type plus or minus to change speed from current value of %d", speech_client.param[S_SPEED]);
    else if (speech_client.change_mode[S_SPEED] == 1 || verbose_mode())
        say_it("Speed control is turned off");
    speech_client.change_mode[S_SPEED] = mode;
}

int get_speed_change_mode( void )
{
    dlg_log(LOG_LOGFILE, "get_speed_change_mode: value=%d", speech_client.change_mode[S_SPEED]);
    return speech_client.change_mode[S_SPEED];
}

int change_speed( int amount )
{
    int old_val = speech_client.param[S_SPEED];
    speech_client.param[S_SPEED] += amount;
    dlg_log(LOG_LOGFILE, "change_speed: old=%d, new=%d", old_val, speech_client.param[S_SPEED]);
    say_it("Speed is now %d", speech_client.param[S_SPEED]);
    return old_val;
}

int set_pitch_change_mode( int mode )
{
    dlg_log(LOG_LOGFILE, "set_pitch_change_mode: new value=%d", mode);
    if (mode)
        say_it("Type plus or minus to change pitch from current value of %d", speech_client.param[S_PITCH]);
    else if (speech_client.change_mode[S_PITCH] == 1 || verbose_mode())
        say_it("Pitch control is turned off");
    speech_client.change_mode[S_PITCH] = mode;
}

int get_pitch_change_mode( void )
{
    dlg_log(LOG_LOGFILE, "get_pitch_change_mode: value=%d", speech_client.change_mode[S_PITCH]);
    return speech_client.change_mode[S_PITCH];
}

int change_pitch( int amount )
{
    int old_val = speech_client.param[S_PITCH];
    speech_client.param[S_PITCH] += amount;
    dlg_log(LOG_LOGFILE, "change_pitch: old=%d, new=%d", old_val, speech_client.param[S_PITCH]);
    say_it("Pitch is now %d", speech_client.param[S_PITCH]);
    return old_val;
}

/*
 * ----------------------------------------------------------------------
 * Final program close. Empty queue, reset run flag, wait for thread exit
 * delete queue.
 * ----------------------------------------------------------------------
 */
int s_close(synth_t *s)
{
    dlg_log(LOG_LOGFILE, "s_close: **** STARTING ****");
    s_clear(s);
    runSynthesize = 0;
    pthread_join(text_thread, NULL);
    tqDelete(textfifo);
    return 0;
}
static void close_audiodev()
{
#if defined(USE_FLITE)
  if (audiodev)
  {
    audio_close(audiodev);
    audiodev = NULL;
  }
#endif
}
#if defined(USE_FLITE)
static void cst_wave_free(cst_wave *w)
{
  dlg_log(LOG_LOGFILE, "cst_wave_free: %p", w);
  if (w)
  {
    cst_free(w->samples);
    cst_free(w);
  }
}
#endif

#if defined(USE_FLITE)
static void *play(synth_t *s, cst_wave *wptr)
{
  int playlen;
  int skip;
  int *sparam = s->state->param;

  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
  while (!audiodev)
  {
    audiodev = audio_open(wptr->sample_rate, wptr->num_channels, CST_AUDIO_LINEAR16);
    if (!audiodev || (int)audiodev->platform_data == -1)
    {
      if (errno == EBUSY)
      {
        dlg_log(LOG_LOGFILE, "Audio device is busy; trying again");
        close_audiodev();
        usleep(20000);
        continue;
      }
      perror("audio_open");
    }
  }
  skip = 1500000 / sparam[S_SPEED];
  playlen = wptr->num_samples - (skip * 2);
  if (playlen > 0 && playlen < 500) playlen += (skip * 2) / 3;
  dlg_log(LOG_LOGFILE, "play: computing skip=%d, new playlen=%d", skip, playlen);
  if (playlen < 0) playlen = 0;
  dlg_log(LOG_LOGFILE, "play: wave=%p, samples=%p, num_samples=%d skip=%d playlen=%d", wptr, wptr->samples, wptr->num_samples, skip, playlen);
  time_left += (float)playlen / wptr->sample_rate;
  if (playlen > 0)
  {
    if (sparam[S_VOLUME] != 1000)
      cst_wave_rescale(wptr, (sparam[S_VOLUME] << 16) / 1000);
    audio_write(audiodev, wptr->samples + skip, playlen * 2);
  }
  dlg_log(LOG_LOGFILE, "play: flushing audio");
  audio_flush(audiodev);
  time_left -= (float)playlen / wptr->sample_rate;
  close_audiodev();
  dlg_log(LOG_LOGFILE, "play: returning");
  return NULL;
}
#endif

static void *synthesize(void *s)
{
#if defined(USE_FLITE)
  cst_wave *wptr = NULL;
#endif
  int wml = 0;	/* wave mutex locked */
  synth_t *sp = s;
  int command;

  dlg_log(LOG_LOGFILE, "synthesize: entering");
  while (runSynthesize)
  {
    while (!textfifo->empty)
    {
      pthread_mutex_lock(textfifo->mut);
      if (!textfifo->empty)
      {
#if defined(USE_FLITE)
        tqGet(textfifo, p_text);
        dlg_log(LOG_LOGFILE, "synthesize: calling flite_text_to_wave: text=%s,  v=%lx", p_text, v);
        wptr = flite_text_to_wave(p_text, v);
#elif defined(USE_ESPEAK)
	// set any non-default values of parameters. This must be done after espeak_Initialize()
	if((speed = speech_client.param[S_SPEED]) > 0)
		espeak_SetParameter(espeakRATE,speed,0);
	if((volume = speech_client.param[S_VOLUME])  >= 0)
		espeak_SetParameter(espeakVOLUME,volume,0);
	if((pitch = speech_client.param[S_PITCH]) >= 0)
		espeak_SetParameter(espeakPITCH,pitch,0);
	if(option_capitals >= 0)
		espeak_SetParameter(espeakCAPITALS,option_capitals,0);
	if(option_punctuation >= 0)
		espeak_SetParameter(espeakPUNCTUATION,option_punctuation,0);
	if(option_linelength > 0)
		espeak_SetParameter(espeakLINELENGTH,option_linelength,0);
	if(option_punctuation == 2)
		espeak_SetPunctuationList(option_punctlist);
        tqGet(textfifo, p_text);
	dlg_log(LOG_LOGFILE, "synthesize: calling espeak_Synth with text=%s", p_text);
	espeak_Synth(p_text,strlen(p_text)+1,0,POS_CHARACTER,0,synth_flags,NULL,NULL);
#endif
      }
      pthread_mutex_unlock(textfifo->mut);
      pthread_cond_signal(textfifo->notFull);
#if defined(USE_FLITE)
      play(sp, wptr);
#endif
    }
  }
  return NULL;
}

/*
 * ----------------------------------------------------------------------
 * Copy text to fifo shared by synthesis thread
 * ----------------------------------------------------------------------
 */
int s_synth(struct synth_struct *s, unsigned char *buffer)
{
  dlg_log(LOG_LOGFILE, "s_synth: **** STARTING ****");
  assert(s->state->initialized);
  pthread_mutex_lock(textfifo->mut);
  while (textfifo->full)
    pthread_cond_wait(textfifo->notFull, textfifo->mut);
  tqAdd(textfifo, buffer);
  pthread_mutex_unlock(textfifo->mut);
  pthread_cond_signal(textfifo->notEmpty);
  return 0;
}

/*
 * ----------------------------------------------------------------------
 * Remove anything in the synthesizer speech queue.
 * ----------------------------------------------------------------------
 */
int s_clear(synth_t *s)
{
  int i;

  dlg_log(LOG_LOGFILE, "s_clear: **** STARTING ****");
  pthread_mutex_lock(textfifo->mut);
  tqEmpty(textfifo);
  pthread_mutex_unlock(textfifo->mut);
  pthread_cond_signal(textfifo->notFull);
#if defined(USE_FLITE)
  dlg_log(LOG_LOGFILE, "s_clear: draining audio device");
  if (audiodev)
    audio_drain(audiodev);
  time_left = 0;
#elif defined(USE_ESPEAK)
  espeak_Cancel();
  dlg_log(LOG_LOGFILE, "s_clear: cancelled synthesis and audio output");
#endif
  return 0;
}

/*
 * ----------------------------------------------------------------------
 * Get a synthesizer parameter.
 * ----------------------------------------------------------------------
 */
int s_get_param(synth_t *s, synth_par_t par, int *value)
{
    if (par >= 0 && par < S_MAX) {
	*value = s->state->param[par];
	return 0;
    } else
	return 1;
}


/*
 * ----------------------------------------------------------------------
 * Set a parameter of the synthesizer.
 * ----------------------------------------------------------------------
 */
int s_set_param(synth_t *s, synth_par_t par, int value)
{
#if defined(USE_FLITE)
    s->state->param[par] = value;
    switch (par) {
      case S_SPEED:               /* default: 1 */
  	dlg_log(LOG_LOGFILE, "Setting duration_stretch to %4.3f", (float)1000 / value);
	feat_set_float(v->features, "duration_stretch", (float)1000 / value);
        break;
      case S_PITCH:               /* default: 100 */
        dlg_log(LOG_LOGFILE, "Setting pitch to %3.3f", exp((float)value / 1000) * 100 / exp(1));
	feat_set_float(v->features, "int_f0_target_mean", exp((float)value / 1000) * 100 / exp(1));
	/* tbd */
        break;
      case S_VOLUME:              /* default: 92, range: 0-100 */
	/* tbd */
        break;
      default:
        return 1;
    }
    return 0;
#endif
}
void setting_add(char *name, char *value)
{
  setting *p = settings;

  if (p == NULL) p = settings = (setting *)malloc(sizeof(setting));
  else
  {
    while (p->next) p = p->next;
    p->next = (setting *)malloc(sizeof(setting));
    p = p->next;
  }
  p->name = strdup(name);
  p->value = strdup(value);
  p->next = NULL;
}

int settings_init()
{
  FILE *fp;
  char buf[513];
  char *p;

  p = getenv("HOME");
  sprintf(buf, "%s/.es.conf", p);
  fp = fopen(buf, "r");
  if (!fp) fp = fopen("/etc/es.conf", "r");
  if (!fp) return 1;
  while (fgets(buf, 512, fp))
  {
    p = buf;
    while (*p >= 32) p++;
    if (*p == 13 || *p == 10) *p = '\0';
    p = buf;
    if (!strncasecmp(buf, "env ", 4))
    {
      putenv(strdup(buf + 4));
      continue;
    }
    while (*p && *p != '=') p++;
    if (*p != '=') continue;
    *p++ = '\0';
    setting_add(buf, p);
  }
  fclose(fp);
  return 0;
}

char *lookup_string(void *context, const char *name)
{
  setting *p;

  if (settings == NULL)
  {
    if (settings_init() == -1)
    {
      fprintf(stderr, "Fatal:  Cannot initialize settings\n");
      exit(1);
    }
  }
  p = settings;
  while (p)
  {
    if (!strcasecmp(p->name, name)) return p->value;
    p = p->next;
  }
  return NULL;
}

int lookup_int(char *name, int defval)
{
  char *val;

  val = lookup_string(NULL, name);
  if (!val) return defval;
  return atoi(val);
}

void es_addtext(CLIENT *client, char *buf)
{
  int i;
  int val;
  char obuf[BUFSIZE];
  unsigned char *p, *q;

  for (i = 0; i < NPARAMS; i++)
  {
    s_get_param(&synthapi, i, &val);
    if (val != client->param[i])
      s_set_param(&synthapi, i, client->param[i]);
  }
  q = (unsigned char *)obuf;
  for (p = (unsigned char *)buf; *p; p++)
  {
    if (q - (unsigned char *)obuf > BUFSIZE - 128)
    {
      *q = 0;
      s_synth(&synthapi, obuf);
      q = obuf;
    }
    if (!client->punct[(int)*p]) *q++ = *p;
    else
    {
      if (q > (unsigned char *)obuf && q[-1] != ' ') *q++ = ' ';
      strcpy(q, ascii[(int)*p]);
      while (*q) q++;
      *q++ = ' ';
    }
  }
  *q = 0;
  s_synth(&synthapi, obuf);
}

void say_widget(const char *type, const char *desc, int selected)
{
    char temp[MAX_LEN];

    dlg_log(LOG_LOGFILE, "say_widget: start");
    *temp = 0;
    if (selected)
        strcat(temp, "Selected ");
    strcat(temp, type);
    strcat(temp, " ");
    strcat(temp, desc);
    es_addtext(&speech_client, temp);
}

void say_char(const char val)
{
    char temp[2];

    dlg_log(LOG_LOGFILE, "say_char: start");
    temp[0] = val;
    temp[1] = '\0';

    es_addtext(&speech_client, temp);
}

void say_bool(const char *desc, bool val)
{
    char temp[1024];

    dlg_log(LOG_LOGFILE, "say_bool: start");
    sprintf(temp, "%s %s", desc, (val)?"TRUE":"FALSE");
    es_addtext(&speech_client, temp);
}

void say_it(const char *text, ...)
{
  char buf[1024];
  va_list arg;
  FILE *fp;
  char logname[256];

  va_start(arg, text);
  vsnprintf(buf, 1024, text, arg);
  va_end(arg);
  es_addtext(&speech_client, buf);
}

void say_int(const char *desc, int val)
{
    char temp[1024];

    dlg_log(LOG_LOGFILE, "say_int: start");
    *temp = 0;
    sprintf(temp, "%s %d", desc, val);
    es_addtext(&speech_client, temp);
}

int speech_control(int key)
{
	int old_val = -1;
        dlg_log(LOG_LOGFILE, "speech_control, got key=%x", key);
        s_clear(synthptr);
	switch (key) {
	case 0x8048: /* 0x8: ctrl-h */
            say_it("Using "
#if defined(USE_FLITE)
                   "FLIghT "
#elif defined(USE_ESPEAK)
                   "E SPEAK "
#endif
                   "text to speech engine.\n");
            say_it("Speech control recognizes the following commands:\n"
                   "Control, A, V, will toggle volume control mode.\n"
                   "Control, A, S, will toggle speed control mode.\n"
                   "Control, A, P, will toggle pitch control mode.\n"
                   "Plus will increment volume, speed, or pitch.\n"
                   "Minus will decrement  volume, speed, or pitch.\n"
                   "Control, A, R, will refresh spoken screen.\n"
                   "Control, A, H, will speak this help message.");
	    key = 0x8888;
	    break;
	case 0x8050: /* 0x10: ctrl-p */
	    set_pitch_change_mode( 1 - get_pitch_change_mode() );
	    set_volume_change_mode(0);
	    set_speed_change_mode(0);
	    key = 0x8888;
	    break;
	case 0x8053: /* */
	    set_speed_change_mode( 1 - get_speed_change_mode() );
	    set_volume_change_mode(0);
	    set_pitch_change_mode(0);
	    key = 0x8888;
	    break;
	case 0x8056: /* 0x16: ctrl-v */
	    set_volume_change_mode( 1 - get_volume_change_mode() );
	    set_pitch_change_mode(0);
	    set_speed_change_mode(0);
	    key = 0x8888;
	    break;
	case 0x2d: /* - */
	    if (get_volume_change_mode() > 0)
		    old_val = change_volume(-VOLUME_INCREMENT);
	    else if (get_speed_change_mode() > 0)
		    old_val = change_speed(-SPEED_INCREMENT);
	    else if (get_pitch_change_mode() > 0)
		    old_val = change_pitch(-PITCH_INCREMENT);
	    if (old_val >= 0) key = 0x8888;
	    break;
	case 0x2b: /* + */
	case 0x3d: /* = */
	    if (get_volume_change_mode() > 0)
		    old_val = change_volume(VOLUME_INCREMENT);
	    else if (get_speed_change_mode() > 0)
		    old_val = change_speed(SPEED_INCREMENT);
	    else if (get_pitch_change_mode() > 0)
		    old_val = change_pitch(PITCH_INCREMENT);
	    if (old_val >= 0) key = 0x8888;
	    break;
	default:
	    break;
	}
	return key;
}

char *reverse_str(char **input) {
	int len = strlen(*input);
	char *reverse = (char *)malloc(len+1);
	int i,j=0;

	for (i=len-1; i>=0; i--) {
		reverse[j++] = (*input)[i];
	}
        reverse[j] = '\0';
	strcpy(*input, reverse);
	free(reverse);
	return *input;
}

char *expand_special(char *input, char *expanded_str, EXPANSION_TYPE type) {
	switch(type) {
		case FILEPATH:
                        return expand_string(input, expanded_str, ascii2);
		case DESCRIPTION:
                        return expand_string(input, expanded_str, ascii);
		case PASSTHRU:
		default:
			return input;
	}
}

char *expand_string(char *input, char *expanded_str, char **expansion_table) {
        return expand_string_max(input, expanded_str, MAX_LEN, expansion_table);
}

char *expand_string_max(char *input, char *expanded_str, int max_expanded, char **expansion_table) {
	int len1 = strlen(input);
	int len2;
	int i,j,k=-1;
	int max_len = max_expanded - 1;

	if (expanded_str == NULL || max_expanded < 1)
		return NULL;
	len2 = 0;
        expanded_str[0] = '\0';
	for (i=0; i<len1; i++) {
		switch(input[i]) {
		case '.':
		case ',':
		case ':':
		case '-':
			len2 += strlen(expansion_table[input[i]]) + 2;
		default:
			len2++;
			break;

		}
		if (len2 > max_len)
			break;
		k = i;
	}
	len1 = (len2 > max_len)? k : len1;
	for (i=0,j=0; i<len1; i++) {
		switch(input[i]) {
		case '.':
		case ',':
		case ':':
		case '-':
			strcat(expanded_str+j," ");
			strcat(expanded_str+j,expansion_table[input[i]]);
			strcat(expanded_str+j," ");
			j += strlen(expansion_table[input[i]]) + 2;
			break;
		default:
		        *(expanded_str+j++) = input[i];
		        *(expanded_str+j) = '\0';
			break;

		}
	}
        expanded_str[j] = '\0';
	return expanded_str;
}

VSTATES
set_next_vstate(VSTATES *vstate, VSTATES *prior_vstate, VSTATES next_vstate)
{
    *prior_vstate = *vstate;
    *vstate = next_vstate;
}

char *
set_say_vstate(VSTATES vstate)
{
	if (vstate < 0) {
		switch (vstate) {
			case vTEXT_PAGE:
				say_what |= DISPLAY_TEXT_PAGE;
				break;
			case vTEXT_LINE:
				say_what |= DISPLAY_TEXT_LINE;
				break;
			case vTEXT:
				say_what |= INPUT_BOX;
				break;
			case vFILES:
				say_what |= DISPLAY_LIST;
				break;
			case vDIRS:
				say_what |= DISPLAY_LIST;
				break;
			case vBUTTONS:
				say_what |= DISPLAY_BUTTON;
				break;
		}
	} else {
		say_what |= DISPLAY_BUTTON;
	}
}

char *
say_vstate(VSTATES vstate)
{
	char temp[256];

	switch (vstate) {
		case vTEXT_LINE:
			say_it("STATE TEXT LINE");
			break;
		case vTEXT:
			say_it("STATE TEXT");
			break;
		case vFILES:
			say_it("STATE FILES");
			break;
		case vDIRS:
			say_it("STATE DIRS");
			break;
		case vBUTTONS:
			say_it("STATE BUTTONS");
			break;
		default:
			sprintf(temp, "STATE BUTTON = %d", vstate);
			say_it(temp);
			break;
	}
}

void client_init(CLIENT *client)
{
  int i;
  for (i = 0; i < NPARAMS; i++) {
    client->param[i] = default_param[i];
    client->change_mode[i] = 0;
  }
  memset(client->punct, 0, sizeof(client->punct));
}
